/**
 * 210824：升级修改【three.js r120-r131】
 * 20220114：为适应XR模式，做了较大改动
 * 20220304:[require] to [import]...jordia
 */
import {Mesh,Raycaster,Vector3,Box3,Vector2,BufferGeometry} from 'three';
import SelectConfig from "./select-config";
import GeometryUtils from "../../general/geometry-utils";

/**
 * 投影几何体（NDC空间）
 */
class SelectGeometry{
    constructor(){
        this.vertices=null;
        this.faces=null;
        this.lines=null;
        this.maxX=Number.NEGATIVE_INFINITY;
        this.maxY=Number.NEGATIVE_INFINITY;
        this.maxZ=Number.NEGATIVE_INFINITY;
        this.minX=Number.POSITIVE_INFINITY;
        this.minY=Number.POSITIVE_INFINITY;
        this.minZ=Number.POSITIVE_INFINITY;
    }
    set vCount(length){
        this.vertices=new Float32Array(length);
    }
    vertex(index,out){
        out.x=this.vertices[index*3];
        out.y=this.vertices[index*3+1];
        out.z=this.vertices[index*3+2];
    }
    set fCount(length){
        this.faces=new Uint16Array(length);
    }
    set lCount(length){
        this.lines=new Uint16Array(length);
    }
    reset(){
        this.vertices=null;
        this.faces=null;
        this.lines=null;
        this.maxX=Number.NEGATIVE_INFINITY;
        this.maxY=Number.NEGATIVE_INFINITY;
        this.maxZ=Number.NEGATIVE_INFINITY;
        this.minX=Number.POSITIVE_INFINITY;
        this.minY=Number.POSITIVE_INFINITY;
        this.minZ=Number.POSITIVE_INFINITY;
    }
    dispose(){
        this.vertices=null;
        this.faces=null;
        this.lines=null;
    }
}

/**
 * 选取mesh的配置
 */
export default class SelectMeshConfig extends SelectConfig{
    constructor(values){
        super(values);
    }
    get type(){
        return 'SelectMeshConfig';
    }
    checkType(object){
        return object instanceof Mesh;
    }
    compare(a, b) {
        return a.uuid===b.uuid;
    }
    _computeNDCBox(camera,pointer1,pointer2){
        camera.getWorldPosition(this._camPos);
        this._look.addVectors(pointer1,pointer2).multiplyScalar(0.5);
        this._look.sub(this._camPos);
        let p1=this._tV0.copy(pointer1).project(camera);
        let p2=this._tV1.copy(pointer2).project(camera);
        let box=this._tB0;
        /**
         * NDC space,camera near-far box
         */
        box.min.set(Math.min(p1.x,p2.x),Math.min(p1.y,p2.y),-1);
        box.max.set(Math.max(p1.x,p2.x),Math.max(p1.y,p2.y),1);
        return box;
    }
    intersectRect(object,pointer1,pointer2,camera){
        if(!object.visible)return false;
        let box=this._computeNDCBox(camera,pointer1,pointer2);
        return this.checkProjectionGeometry(object,box,camera);
    }
    checkProjectionGeometry(object,box,camera){
        /**
         * opposite to select direction
         */
        object.getWorldPosition(this._tV3);
        this._tV3.sub(this._camPos);
        if(this._look.dot(this._tV3)<0){
            return false;
        }
        let pg=this._projGeometry;
        this._projectGeometry(object,pg,camera);
        /**
         * out of camera near-far range
         */
        if(pg.minZ<-1)return false;
        if(pg.maxZ>1)return false;
        /**
         * NDC space , intersect bound box
         */
        let objectRect=this._tB1;
        objectRect.min.set(pg.minX,pg.minY,pg.minZ);
        objectRect.max.set(pg.maxX,pg.maxY,pg.maxZ);
        if (!box.intersectsBox(objectRect)){
            return false;
        }
        /**
         * select box contains vertex
         */
        for (let i=0;i<pg.vertices.length/3;i++) {
            pg.vertex(i,this._tV1);
            if (box.containsPoint(this._tV1)){
                return true;
            }
        }
        /**
         * edge intersects select box
         */
        for (let j =0;j<pg.lines.length/2;j++) {
            pg.vertex(pg.lines[j*2],this._tV0);
            pg.vertex(pg.lines[j*2+1],this._tV1);
            if (GeometryUtils.lineIntersectsBox(this._tV0,this._tV1,box,2,true)){
                return true;
            }
        }
        /**
         * face contains one of select box vertex
         */
        let rectPoint = this._tV4.set(box.max.x, box.max.y);
        for (let i =0;i< pg.faces.length/3;i++) {
            pg.vertex(pg.faces[i*3],this._tV0);
            pg.vertex(pg.faces[i*3+1],this._tV1);
            pg.vertex(pg.faces[i*3+2],this._tV2);
            if (GeometryUtils.ifPointInTriangle(this._tV0,this._tV1,this._tV2, rectPoint)){
                return true;
            }
        }
        return false;
    }
    intersectPointer(object,raycaster,info){
        if(!object.visible)return false;
        this._raycaster=raycaster;
        return this._raycaster.intersectObject(object, false,info);
    }
    dispose() {
        super.dispose();
        this._projGeometry.dispose();
    }

    _initVariable() {
        super._initVariable();
        this._projGeometry=new SelectGeometry();
        this._raycaster=new Raycaster();
        this._tV0=new Vector3();
        this._tV1=new Vector3();
        this._tV2=new Vector3();
        this._tB0=new Box3();
        this._tB1=new Box3();
        this._tV3=new Vector3();
        this._look=new Vector3();
        this._camPos=new Vector3();
        this._tV4=new Vector2();
    }
    _projectGeometry(object,out,camera){
        out.reset();
        let trsMatrix = object.matrixWorld;
        if(object.geometry) {
            if (object.geometry instanceof BufferGeometry) {
                let ps = object.geometry.getAttribute('position').array;
                let range = object.geometry.drawRange;
                let count = range.count === Number.POSITIVE_INFINITY ? ps.length : range.count;
                out.vCount = count;
                for (let i = 0; i < count; i += 3) {
                    this._tV3.set(ps[i + range.start], ps[i + 1 + range.start], ps[i + 2 + range.start]);
                    this._tV3.applyMatrix4(trsMatrix).project(camera);
                    let x = this._tV3.x;
                    let y = this._tV3.y;
                    let z = this._tV3.z;
                    if(out.maxX<x)out.maxX=x;
                    if(out.maxY<y)out.maxY=y;
                    if(out.maxZ<z)out.maxZ=z;
                    if(out.minX>x)out.minX=x;
                    if(out.minY>y)out.minY=y;
                    if(out.minZ>z)out.minZ=z;
                    out.vertices[i] = x;
                    out.vertices[i + 1] = y;
                    out.vertices[i + 2] = z;
                }
                if(object.isMesh) {
                    if (object.geometry.index) {
                        let indices = object.geometry.index.array;
                        out.fCount=indices.length;
                        for (let i = 0; i < indices.length; i ++) {
                            out.faces[i]=indices[i];
                        }
                    } else {
                        let ps = object.geometry.getAttribute('position').array;
                        out.fCount=ps.length;
                        for (let i = 0; i < ps.length; i ++) {
                            out.faces[i]=i;
                        }
                    }
                }
            }
        }
        if(!out.faces){
            let vl=out.vertices.length/3;
            out.lCount=(vl-1)*2;
            for(let i=0;i<vl-1;i++){
                out.lines.push(i,i+1);
            }
        }else {
            let fl=out.faces.length/3;
            out.lCount=fl*6;
            for (let i=0;i<fl;i++) {
                let a = out.faces[i*3];
                let b = out.faces[i*3+1];
                let c = out.faces[i*3+2];
                out.lines[i*6]=a;
                out.lines[i*6+1]=b;
                out.lines[i*6+2]=b;
                out.lines[i*6+3]=c;
                out.lines[i*6+4]=c;
                out.lines[i*6+5]=a;
            }
        }
    }
}